import os
import json
import pygame as pg
from .. import setup, tools
from .. import constants as c
from ..components import powerup


class Player(pg.sprite.Sprite):
    '''
    玩家类，负责玩家控制，移动，状态切换，动画切换，状态显示
    状态：
    STAND 站立
    WALK 行走，通过是否按下action键切换奔跑/行走。速度是均匀加速到最大速度。
    JUMP 跳跃，向上事初始时速度最大，逐步减小。向下降落时，初始速度最小，逐步变大。
    , FALL, DEATH_JUMP, FLAGPOLE, WALK_AUTO 自动行走，不处理事件，关卡结束时，跳旗子时出现, END_OF_LEVEL_FALL,
    IN_CASTLE, SMALL_TO_BIG, BIG_TO_SMALL, BIG_TO_FIRE, DOWN_TO_PIPE, UP_OUT_PIPE
    动画：每个状态有不同的动画帧，
    '''

    def __init__(self, player_name):
        pg.sprite.Sprite.__init__(self)
        self.player_name = player_name
        self.load_data()
        self.setup_timer()
        self.setup_state()
        self.setup_speed()
        self.load_images()

        if c.DEBUG:
            self.right_frames = self.big_fire_frames[0]
            self.left_frames = self.big_fire_frames[1]
            self.big = True
            self.fire = True

        self.frame_index = 0
        self.state = c.WALK
        self.image = self.right_frames[self.frame_index]
        self.rect = self.image.get_rect()

    def restart(self):
        '''restart after player is dead or go to next level'''
        if self.dead:
            self.dead = False
            self.big = False
            self.fire = False
            self.set_player_image(self.small_normal_frames, 0)
            self.right_frames = self.small_normal_frames[0]
            self.left_frames = self.small_normal_frames[1]
        self.state = c.STAND

    def load_data(self):
        player_file = str(self.player_name) + '.json'
        file_path = os.path.join('source', 'data', 'player', player_file)
        f = open(file_path)
        self.player_data = json.load(f)

    def setup_timer(self):
        self.walking_timer = 0
        self.death_timer = 0
        self.flagpole_timer = 0
        self.transition_timer = 0  # 过渡定时器，变大变小变开火时能用
        self.hurt_invincible_timer = 0
        self.invincible_timer = 0  # 无敌计时器
        self.last_fireball_time = 0

    def setup_state(self):
        self.facing_right = True
        self.allow_jump = True
        self.allow_fireball = True
        self.dead = False
        self.big = False
        self.fire = False
        self.hurt_invincible = False
        self.invincible = False
        self.crouching = False

    def setup_speed(self):
        speed = self.player_data[c.PLAYER_SPEED]
        self.x_vel = 0
        self.y_vel = 0

        self.max_walk_vel = speed[c.MAX_WALK_SPEED]
        self.max_run_vel = speed[c.MAX_RUN_SPEED]
        self.max_y_vel = speed[c.MAX_Y_VEL]
        self.walk_accel = speed[c.WALK_ACCEL]
        self.run_accel = speed[c.RUN_ACCEL]
        self.jump_vel = speed[c.JUMP_VEL]  # 取自json里的配置值，为-10.5

        self.gravity = c.GRAVITY
        self.max_x_vel = self.max_walk_vel
        self.x_accel = self.walk_accel

    def load_images(self):
        sheet = setup.GFX['mario_bros']
        frames_list = self.player_data[c.PLAYER_FRAMES]

        self.right_frames = []
        self.left_frames = []

        self.right_small_normal_frames = []
        self.left_small_normal_frames = []
        self.right_big_normal_frames = []
        self.left_big_normal_frames = []
        self.right_big_fire_frames = []
        self.left_big_fire_frames = []

        for name, frames in frames_list.items():
            for frame in frames:
                image = tools.get_image(sheet, frame['x'], frame['y'], frame['width'], frame['height'], c.BLACK, c.SIZE_MULTIPLIER, True)
                left_image = pg.transform.flip(image, True, False)

                if name == c.RIGHT_SMALL_NORMAL:
                    self.right_small_normal_frames.append(image)
                    self.left_small_normal_frames.append(left_image)
                elif name == c.RIGHT_BIG_NORMAL:
                    self.right_big_normal_frames.append(image)
                    self.left_big_normal_frames.append(left_image)
                elif name == c.RIGHT_BIG_FIRE:
                    self.right_big_fire_frames.append(image)
                    self.left_big_fire_frames.append(left_image)

        self.small_normal_frames = [self.right_small_normal_frames, self.left_small_normal_frames]
        self.big_normal_frames = [self.right_big_normal_frames, self.left_big_normal_frames]
        self.big_fire_frames = [self.right_big_fire_frames, self.left_big_fire_frames]

        self.all_images = [
            self.right_small_normal_frames,
            self.left_small_normal_frames,
            self.right_big_normal_frames,
            self.left_big_normal_frames,
            self.right_big_fire_frames,
            self.left_big_fire_frames,
        ]

        self.right_frames = self.small_normal_frames[0]
        self.left_frames = self.small_normal_frames[1]

    def update(self, keys, game_info, fire_group):
        self.current_time = game_info[c.CURRENT_TIME]
        self.handle_state(keys, fire_group)
        self.check_if_hurt_invincible()
        self.check_if_invincible()
        self.animation()

    def handle_state(self, keys, fire_group):
        if self.state == c.STAND:
            self.standing(keys, fire_group)
        elif self.state == c.WALK:
            self.walking(keys, fire_group)
        elif self.state == c.JUMP:
            self.jumping(keys, fire_group)
        elif self.state == c.FALL:
            self.falling(keys, fire_group)
        elif self.state == c.DEATH_JUMP:
            self.jumping_to_death()
        elif self.state == c.FLAGPOLE:
            self.flag_pole_sliding()
        elif self.state == c.WALK_AUTO:
            self.walking_auto()
        elif self.state == c.END_OF_LEVEL_FALL:
            self.y_vel += self.gravity
        elif self.state == c.IN_CASTLE:
            self.frame_index = 0
        elif self.state == c.SMALL_TO_BIG:
            self.changing_to_big()
        elif self.state == c.BIG_TO_SMALL:
            self.changing_to_small()
        elif self.state == c.BIG_TO_FIRE:
            self.changing_to_fire()
        elif self.state == c.DOWN_TO_PIPE:
            self.y_vel = 1
            self.rect.y += self.y_vel
        elif self.state == c.UP_OUT_PIPE:
            self.y_vel = -1
            self.rect.y += self.y_vel
            if self.rect.bottom < self.up_pipe_y:
                self.state = c.STAND

    def check_to_allow_jump(self, keys):
        if not keys[tools.keybinding['jump']]:
            self.allow_jump = True

    def check_to_allow_fireball(self, keys):
        if not keys[tools.keybinding['action']]:
            self.allow_fireball = True

    def standing(self, keys, fire_group):
        self.check_to_allow_jump(keys)
        self.check_to_allow_fireball(keys)

        self.frame_index = 0
        self.x_vel = 0
        self.y_vel = 0

        if keys[tools.keybinding['action']]:
            if self.fire and self.allow_fireball:
                self.shoot_fireball(fire_group)

        if keys[tools.keybinding['down']]:
            self.update_crouch_or_not(True)

        if keys[tools.keybinding['left']]:
            self.facing_right = False
            self.update_crouch_or_not()
            self.state = c.WALK
        elif keys[tools.keybinding['right']]:
            self.facing_right = True
            self.update_crouch_or_not()
            self.state = c.WALK
        elif keys[tools.keybinding['jump']]:
            if self.allow_jump:
                self.state = c.JUMP
                self.y_vel = self.jump_vel

        if not keys[tools.keybinding['down']]:
            self.update_crouch_or_not()

    def update_crouch_or_not(self, isDown=False):
        '''更新蹲下或者站立'''
        if not self.big:
            self.crouching = True if isDown else False
            return
        if not isDown and not self.crouching:
            return

        self.crouching = True if isDown else False
        frame_index = 7 if isDown else 0  # 第7帧是蹲下的动作
        bottom = self.rect.bottom
        left = self.rect.x
        if self.facing_right:
            self.image = self.right_frames[frame_index]
        else:
            self.image = self.left_frames[frame_index]
        self.rect = self.image.get_rect()
        self.rect.bottom = bottom
        self.rect.x = left
        self.frame_index = frame_index

    def walking(self, keys, fire_group):
        self.check_to_allow_jump(keys)
        self.check_to_allow_fireball(keys)

        if self.frame_index == 0:
            self.frame_index += 1
            self.walking_timer = self.current_time
        elif self.current_time - self.walking_timer > self.calculate_animation_speed():
            if self.frame_index < 3:
                self.frame_index += 1
            else:
                self.frame_index = 1
            self.walking_timer = self.current_time

        # action可以变成奔跑状态，速度更快。action可以是切换奔跑，也可以是发送火球
        if keys[tools.keybinding['action']]:
            self.max_x_vel = self.max_run_vel
            self.x_accel = self.run_accel
            if self.fire and self.allow_fireball:
                self.shoot_fireball(fire_group)
        else:
            self.max_x_vel = self.max_walk_vel
            self.x_accel = self.walk_accel

        if keys[tools.keybinding['jump']]:
            if self.allow_jump:
                self.state = c.JUMP
                if abs(self.x_vel) > 4:
                    self.y_vel = self.jump_vel - 0.5  # x轴速度大于4时，起跳的速度再打一些（-10.5-0.5=-11）
                else:
                    self.y_vel = self.jump_vel

        if keys[tools.keybinding['left']]:
            self.facing_right = False
            if self.x_vel > 0:
                self.frame_index = 5
                self.x_accel = c.SMALL_TURNAROUND

            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)
        elif keys[tools.keybinding['right']]:
            # print(str(self.current_time) + 'key right')
            self.facing_right = True
            if self.x_vel < 0:
                self.frame_index = 5
                self.x_accel = c.SMALL_TURNAROUND

            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
        else:
            # print(str(self.current_time) + 'no key, slow down')
            if self.facing_right:
                if self.x_vel > 0:
                    self.x_vel -= self.x_accel
                else:
                    self.x_vel = 0
                    self.state = c.STAND  # 没有任何的有效按键，状态切换为STAND
            else:
                if self.x_vel < 0:
                    self.x_vel += self.x_accel
                else:
                    self.x_vel = 0
                    self.state = c.STAND  # 没有任何的有效按键，状态切换为STAND

    def jumping(self, keys, fire_group):
        """该方法只负责改变y_vel的值，具体的坐标改动在Level的update_player_position方法里实现。
        y_vel 的值大于0时是下降，小于0是上升。
        起初时y_vel=-10.5，逐渐增大，大于0时改为下落。根据debug日志，前期帧每帧跳跃的高度大，后期逐渐变小，模拟了现实中的跳跃场景"""
        # 开始记录初始位置
        # if not hasattr(self, 'jump_start_y'):
        #     self.jump_start_y = self.rect.y
        #     self.max_height_in_jump = 0

        # 更新帧达到的最大跳跃高度,y方向初始位置在最下，值最大
        # current_jump_height = self.jump_start_y - self.rect.y
        # if current_jump_height > self.max_height_in_jump:
        #     self.max_height_in_jump = current_jump_height

        self.check_to_allow_fireball(keys)

        self.allow_jump = False
        self.frame_index = 4
        self.gravity = c.JUMP_GRAVITY
        self.y_vel += self.gravity

        # y_vel>=0就是到达最高点了，切换状态为降落,避免无限上跳
        if self.y_vel >= 0 and self.y_vel < self.max_y_vel:
            self.gravity = c.GRAVITY
            self.state = c.FALL
            # 在这里打印最大跳跃高度
            # print(f"本次跳跃最大高度: {self.max_height_in_jump} 像素")
            # delattr(self, 'jump_start_y')

        if keys[tools.keybinding['right']]:
            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
        elif keys[tools.keybinding['left']]:
            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)

        # 如果没有长按jump键，就切换为下降状态，实现了长短按高度不一样
        if not keys[tools.keybinding['jump']]:
            self.gravity = c.GRAVITY
            self.state = c.FALL
            # 在这里打印最大跳跃高度 跳出屏幕外汇报错，需要查一下bug
            # print(f"本次跳跃最大高度: {self.max_height_in_jump} 像素")
            # delattr(self, 'jump_start_y')

        if keys[tools.keybinding['action']]:
            if self.fire and self.allow_fireball:
                self.shoot_fireball(fire_group)

    def falling(self, keys, fire_group):
        '''处理下落的状态'''
        self.check_to_allow_fireball(keys)
        self.y_vel = self.cal_vel(self.y_vel, self.max_y_vel, self.gravity)

        if keys[tools.keybinding['right']]:
            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
        elif keys[tools.keybinding['left']]:
            self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)

        if keys[tools.keybinding['action']]:
            if self.fire and self.allow_fireball:
                self.shoot_fireball(fire_group)

    def jumping_to_death(self):
        '''死亡时也有一段动画'''
        if self.death_timer == 0:
            self.death_timer = self.current_time
        elif (self.current_time - self.death_timer) > 500:
            self.rect.y += self.y_vel
            self.y_vel += self.gravity

    def cal_vel(self, vel, max_vel, accel, isNegative=False):
        """max_vel and accel must > 0"""
        if isNegative:
            new_vel = vel * -1
        else:
            new_vel = vel
        if (new_vel + accel) < max_vel:
            new_vel += accel
        else:
            new_vel = max_vel
        if isNegative:
            return new_vel * -1
        else:
            return new_vel

    def calculate_animation_speed(self):
        '''计算动画的速度，保证流畅度。walk和walking_auto方法中，计算两帧之间的时间差，如果大于计算速度，就切换动画
        所以当计算的速度变小，会提前切换动画，实现了玩家的速度越快动画切换的越快的效果'''
        if self.x_vel == 0:
            animation_speed = 130
        elif self.x_vel > 0:
            animation_speed = 130 - (self.x_vel * 13)
        else:
            animation_speed = 130 - (self.x_vel * 13 * -1)
        return animation_speed

    def shoot_fireball(self, powerup_group):
        '''如果上次发射的时间差小于500毫秒，则不允许发射火球，就是什么都不做'''
        if (self.current_time - self.last_fireball_time) > 500:
            self.allow_fireball = False
            powerup_group.add(powerup.FireBall(self.rect.right, self.rect.y, self.facing_right))
            self.last_fireball_time = self.current_time
            self.frame_index = 6

    def flag_pole_sliding(self):
        '''处理玩家在旗杆上的移动'''
        self.state = c.FLAGPOLE  # 感觉这一句可以删除
        self.x_vel = 0
        self.y_vel = 5

        if self.flagpole_timer == 0:
            self.flagpole_timer = self.current_time
        elif self.rect.bottom < 493:
            if (self.current_time - self.flagpole_timer) < 65:
                self.frame_index = 9
            elif (self.current_time - self.flagpole_timer) < 130:
                self.frame_index = 10
            else:
                self.flagpole_timer = self.current_time
        elif self.rect.bottom >= 493:
            self.frame_index = 10

    def walking_auto(self):
        self.max_x_vel = 5
        self.x_accel = self.walk_accel

        self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)

        if self.walking_timer == 0 or (self.current_time - self.walking_timer) > 200:
            self.walking_timer = self.current_time
        elif self.current_time - self.walking_timer > self.calculate_animation_speed():
            if self.frame_index < 3:
                self.frame_index += 1
            else:
                self.frame_index = 1
            self.walking_timer = self.current_time

    def changing_to_big(self):
        '''变大的动画效果。其实就是在不同时间段timer_list里显示不同的大小，实现闪烁效果'''
        timer_list = [135, 200, 365, 430, 495, 560, 625, 690, 755, 820, 885]
        # size value 0:small, 1:middle, 2:big
        size_list = [1, 0, 1, 0, 1, 2, 0, 1, 2, 0, 2]
        frames = [(self.small_normal_frames, 0), (self.small_normal_frames, 7), (self.big_normal_frames, 0)]
        if self.transition_timer == 0:
            self.big = True
            self.change_index = 0
            self.transition_timer = self.current_time
        elif (self.current_time - self.transition_timer) > timer_list[self.change_index]:
            if (self.change_index + 1) >= len(timer_list):
                # player becomes big
                self.transition_timer = 0
                self.set_player_image(self.big_normal_frames, 0)
                self.state = c.WALK
                self.right_frames = self.right_big_normal_frames
                self.left_frames = self.left_big_normal_frames
            else:
                frame, frame_index = frames[size_list[self.change_index]]
                self.set_player_image(frame, frame_index)
            self.change_index += 1

    def changing_to_small(self):
        timer_list = [265, 330, 395, 460, 525, 590, 655, 720, 785, 850, 915]
        # size value 0:big, 1:middle, 2:small
        size_list = [0, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
        frames = [(self.big_normal_frames, 4), (self.big_normal_frames, 8), (self.small_normal_frames, 8)]

        if self.transition_timer == 0:
            self.change_index = 0
            self.transition_timer = self.current_time
        elif (self.current_time - self.transition_timer) > timer_list[self.change_index]:
            if (self.change_index + 1) >= len(timer_list):
                # player becomes small
                self.transition_timer = 0
                self.set_player_image(self.small_normal_frames, 0)
                self.state = c.WALK
                self.big = False
                self.fire = False
                self.hurt_invincible = True
                self.right_frames = self.right_small_normal_frames
                self.left_frames = self.left_small_normal_frames
            else:
                frame, frame_index = frames[size_list[self.change_index]]
                self.set_player_image(frame, frame_index)
            self.change_index += 1

    def changing_to_fire(self):
        timer_list = [65, 195, 260, 325, 390, 455, 520, 585, 650, 715, 780, 845, 910, 975]
        # size value 0:fire, 1:big green, 2:big red, 3:big black
        size_list = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1]
        frames = [(self.big_fire_frames, 3), (self.big_normal_frames, 3), (self.big_fire_frames, 3), (self.big_normal_frames, 3)]

        if self.transition_timer == 0:
            self.change_index = 0
            self.transition_timer = self.current_time
        elif (self.current_time - self.transition_timer) > timer_list[self.change_index]:
            if (self.change_index + 1) >= len(timer_list):
                # player becomes fire
                self.transition_timer = 0
                self.set_player_image(self.big_fire_frames, 3)
                self.fire = True
                self.state = c.WALK
                self.right_frames = self.right_big_fire_frames
                self.left_frames = self.left_big_fire_frames
            else:
                frame, frame_index = frames[size_list[self.change_index]]
                self.set_player_image(frame, frame_index)
            self.change_index += 1

    def set_player_image(self, frames, frame_index):
        self.frame_index = frame_index
        if self.facing_right:
            self.right_frames = frames[0]
            self.image = frames[0][frame_index]
        else:
            self.left_frames = frames[1]
            self.image = frames[1][frame_index]
        bottom = self.rect.bottom
        centerx = self.rect.centerx
        self.rect = self.image.get_rect()
        self.rect.bottom = bottom
        self.rect.centerx = centerx

    def check_if_hurt_invincible(self):
        '''大变小时，是无敌的，一个2秒的无敌动画.
        如果不执行该方法，就不会不显示图像
        '''
        if self.hurt_invincible:
            if self.hurt_invincible_timer == 0:
                self.hurt_invincible_timer = self.current_time
                self.hurt_invincible_timer2 = self.current_time
            elif (self.current_time - self.hurt_invincible_timer) < 2000:
                if (self.current_time - self.hurt_invincible_timer2) < 35:
                    self.image.set_alpha(0)  # Alpha值控制图像的透明度，0完全透明，255完全不透明
                elif (self.current_time - self.hurt_invincible_timer2) < 70:
                    self.image.set_alpha(255)
                    self.hurt_invincible_timer2 = self.current_time
            else:
                self.hurt_invincible = False
                self.hurt_invincible_timer = 0
                for frames in self.all_images:
                    for image in frames:
                        image.set_alpha(255)

    def check_if_invincible(self):
        if self.invincible:
            if self.invincible_timer == 0:
                self.invincible_timer = self.current_time
                self.invincible_timer2 = self.current_time
            elif (self.current_time - self.invincible_timer) < 10000:
                if (self.current_time - self.invincible_timer2) < 35:
                    self.image.set_alpha(0)
                elif (self.current_time - self.invincible_timer2) < 70:
                    self.image.set_alpha(255)
                    self.invincible_timer2 = self.current_time
            elif (self.current_time - self.invincible_timer) < 12000:
                if (self.current_time - self.invincible_timer2) < 100:
                    self.image.set_alpha(0)
                elif (self.current_time - self.invincible_timer2) < 200:
                    self.image.set_alpha(255)
                    self.invincible_timer2 = self.current_time
            else:
                self.invincible = False
                self.invincible_timer = 0
                for frames in self.all_images:
                    for image in frames:
                        image.set_alpha(255)

    def animation(self):
        if self.facing_right:
            self.image = self.right_frames[self.frame_index]
        else:
            self.image = self.left_frames[self.frame_index]

    def start_death_jump(self, game_info):
        self.dead = True
        self.y_vel = -11
        self.gravity = 0.5
        self.frame_index = 6
        self.state = c.DEATH_JUMP
